#include "preprocess.hpp"

// Get the next statement. Must not
// fail for a standard non-empty log.
// Removes comments and reduces any
// whitespace anywhere to a single
// space character. This has been
// coded only for a log NOT devoid
// of any valid statements, else
// set ignore_empty = true to avoid
// an assert (and put other checks
// in main() to recognize the empty
// string returned).
string get_next( ifstream& logfile, unsigned& len, const bool& ignore_empty = true ){

	static const regex trailing_comments( "(-)(-)+.*$" );
	static const RE check_stat( "^"+date+"(\\s+).+?\\[.+?\\].+?:\\s+", icase );
	static const regex whitespace( "\\s+" );
	static const regex extended_comments( "\\s*(/\\*).*?(\\*/)\\s*" );

	string extracted = blank;

	int pos = 0;
	string line; len = 0;
	while( logfile.good() ){

		pos = logfile.tellg();
		getline( logfile, line );
		line = regex_replace( line, trailing_comments, "" );

		if( line.empty() ) continue;
		if( !check_stat.PartialMatch( line ) ) continue;

		string s = line.substr(40) + " "; len++;
		while( logfile.good() ){

			pos = logfile.tellg();
			getline( logfile, line );
			line = regex_replace( line, trailing_comments, "" );

			if( !line.empty() ){
				if( !check_stat.PartialMatch( line ) ){
					s.append( line + " " ); len++;
				}
				else{ logfile.seekg( pos ); break; }
			}

		}

		// Assumes there is no nesting (or use of
		// both types of comments simultaneously).
		s = regex_replace( s, whitespace, " " );
		s = regex_replace( s, extended_comments, "" );

		extracted = s;
		break;

	}

	// This may fail only if there is
	// not a single statement in the
	// given log, which is unlikely:
	// thus its a dispensable check.
	if( !ignore_empty )
		assert( !extracted.empty() );
	return extracted;

}

// There are a few isolated (and syntactically valid) cases of
// multiple queries being logged in a single line, separated by
// a semi-colon, with the exception being strings (enclosed in
// single quotes) in statements, which may contain a ';'. Based
// on the usage in the sample log, we have worked a hack into
// the code*, which is not strictly sufficient, but seems quite
// safe to use. If the marked condition is removed, we WILL end
// up with incomplete (either at the start or end) queries.
// USE ONLY A SINGLE CHARACTER AS DELIMITER AND APPROPRIATELY
// MODIFY THE HACK BELOW TOO (DELIBERATELY DONE TO ENSURE ***
// THAT THIS FUNCTION IS NOT MISTAKENLY USED INCORRECTLY).
strings conquer_multiple( const string& s, const char delimiters[] = ";" ){

	strings tokens;

	char *str = new char[ s.length()+10 ];
	strcpy( str, s.c_str() );

	char * between = strtok( str, delimiters );
	while( between != NULL ){

		if( between[0] == '\'' and !tokens.empty() )
			tokens.at( tokens.size()-1 ).append( // *The hack.
					string( ";" ) + string( between ) );
				// *** Once well understood, one can
				// use delimiters instead of the manually
				// specified string (";") in the above
				// line if (and when) reused elsewhere.
		else{
			string trimmed_between = between;
			trim( trimmed_between );
			if( !trimmed_between.empty() )
				tokens.push_back( trimmed_between );
		}

		between = strtok( NULL, delimiters );

	}

	return tokens;

}

// This function merely finds instances of
// "SELECT" sub-queries and removes them
// from the original query with a substi-
// -tution (obvious from the code below).
void unroll( strings& extracted ){

	static const RE encl_subq( "(\\(\\s*SELECT(\\s+).*)$", icase );
	static const regex whitespace( "\\s+" );
	static const regex commas( "\\s*,\\s*" );

	for( unsigned i = 0; i < extracted.size(); i++ ){

		string s = extracted.at( i );
		for( string remove_found;
			encl_subq.PartialMatch( s, &remove_found );
			extracted.push_back( remove_found )
		){
			remove_found = enclosed( remove_found );
			replace_first( s, remove_found, "S#E#L#E#C#T#I#O#N" );
		}

		trim( s ); s = regex_replace( s, commas, ", " );
		s = regex_replace( s, whitespace, " " );

		extracted.at( i ) = s;

	}

}

// Not all statements are of use. We try to extract out
// "SELECT" (sub)queries (after some necessary cleanup
// to remove the initial log stuff) and leave the rest.
// (It's just the smarter cousin of the plain unroll!)
// The choice of name is due to the fact that we cannot
// use the output to count the *total* statistics...
strings non_counting_next( const string& s ){

	static const RE select_stat( "^DEBUG:(\\s+)QUERY:(\\s+)SELECT(\\s+)", icase );
	static const RE subquery( "\\b(SELECT(\\s+).*)$", icase );

	strings extracted;

	if( select_stat.PartialMatch( s ) ){
		extracted.push_back( s.substr(14) );
		unroll( extracted );
	}
	else if( subquery.PartialMatch( s ) ){

		extracted.push_back( s );
		unroll( extracted );

		// Only the first statement may not be (or have a) "SELECT"
		// (sub)query (since the rest must be the extracted "SELECT"
		// sub-queries). Thus only the first statement needs to be
		// checked after extraction. We assume that any "SELECT"
		// sub-query in the statement can be parsed till then very
		// end of the statement, i.e. in "KEYOWRD ... >SELECT ...<"
		// we assume that the "SELECT" query ends at the very end.
		string partial;
		if( !subquery.PartialMatch( extracted.at(0), &partial ) )
			extracted.erase( extracted.begin() );
		else extracted.at(0) = partial;

	}

	return extracted;

}

// Appends v2 at the end of v1. Just made to avoid
// unbearably long lines due to descriptive names
// of vectors-of-string (i.e "strings" type) used.
inline void post_append( strings& v1, const strings& v2 ){
	v1.insert( v1.end(), v2.begin(), v2.end() );
}

strings get_all_select( const string& statement ){

	strings extracted, temp_separated = conquer_multiple( statement );

	for( int i = 0, l = temp_separated.size(); i < l; i++ )
		post_append( extracted, non_counting_next( temp_separated.at( i ) ) );

	return extracted;

}

